package top.zk123.chain.wechat;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.shiro.SecurityUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import top.zk123.chain.bean.*;
import top.zk123.chain.bean.returnObj.RevokeObj;
import top.zk123.chain.exception.*;
import top.zk123.chain.mapper.CertificateMapper;
import top.zk123.chain.mapper.MessageMapper;
import top.zk123.chain.mapper.RevokeMapper;
import top.zk123.chain.mapper.UserMapper;
import top.zk123.chain.util.AESUtil;
import top.zk123.chain.util.GlobalUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import static top.zk123.chain.util.AESUtil.encode;
import static top.zk123.chain.util.GlobalUtils.*;

/**
 * 微信小程序 业务逻辑实现层
 *
 * @author Ke Zhang
 * @since 2.1
 */
@Service
public class WechatServiceImp implements WechatService {
    final String AppId = "wx1e607a28b8b5d59a";
    final String AppSecret = "64febf8161b8a957b1d7f93a4b55b472";

    @Resource
    RestTemplate restTemplate;
    @Resource
    UserMapper userMapper;
    @Resource
    MessageMapper messageMapper;
    @Resource
    CertificateMapper certificateMapper;
    @Resource
    RevokeMapper revokeMapper;

    @Override
    public String getOpenId(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid={1}&secret={2}&js_code={3}&grant_type=authorization_code";
        ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class, AppId, AppSecret, code);
        JSONObject jsonObject = JSON.parseObject(entity.getBody());
        String openId = (String) jsonObject.get("openid");
        String sessionKey = (String) jsonObject.get("session_key");
        return openId;
    }

    @Override
    public ReturnInfo wxLogin(String code) {
        String openId = getOpenId(code);
        User user = userMapper.findByOpenId(openId);
        if (user == null) {
            user = new User();
            user.setOpenId(openId);
        }
        userMapper.save(user);
        String token = makeToken(openId);
        return new ReturnInfo(200, token, JSON.toJSON(user));
    }

    @Override
    public ReturnInfo wxUpdate(String company, String education, String school) {
        User local = userMapper.findByOpenId(getOpenIdByToken());
        if (!company.equals("")) {
            local.setCompany(company);
        }
        if (!education.equals("")) {
            local.setEducation(education);
        }
        if (!school.equals("")) {
            local.setSchool(school);
        }
        userMapper.save(local);
        return new ReturnInfo(200, "用户信息修改成功", local);
    }


    @Override
    public ReturnInfo wxMessage() {
        String openId = getOpenIdByToken();
        User user = userMapper.findByOpenId(openId);
        String userId = user.getUserId();
        List<Message> messageList = messageMapper.findByReceiver(userId);
        return new ReturnInfo(200, "message查询成功", JSON.toJSON(messageList));
    }


    @Override
    public ReturnInfo wxMessageStateChange(String certUId) {
        // 更新授权状态, 将证书存储至证书包
        Message message = messageMapper.findByCertUID(certUId);
        message.setState("已授权");
        messageMapper.save(message);
        return new ReturnInfo(200, "证书授权成功", null);
    }


    @Override
    public ReturnInfo wxGetCertList(boolean findAll) {
        // token -> openId -> userId -> message
        String openId = getOpenIdByToken();
        String userId = userMapper.findByOpenId(openId).getUserId();
        List<Message> messageList = messageMapper.findByReceiverAndState(userId);
        List<Certificate> certList = new ArrayList<>();
        if (findAll) {
            for (Message message : messageList) {
                Certificate cert = certificateMapper.findValidByUid(message.getCert_uid());
                if (cert != null) {
                    certList.add(cert);
                }
            }
        } else {
            int max = Math.min(messageList.size(), 2);
            for (int i = 0; i < max; i++) {
                Message message = messageList.get(i);
                Certificate cert = certificateMapper.findValidByUid(message.getCert_uid());
                if (cert != null) {
                    certList.add(cert);
                }
            }
        }
        // 排序（按照本硕博排序）
        List<Certificate> res = new ArrayList<>();
        for (Certificate cert : certList) {
            if (cert.getCertType().equals("本科学位证书")) {
                res.add(cert);
            }
        }
        for (Certificate cert : certList) {
            if (cert.getCertType().equals("硕士学位证书")) {
                res.add(cert);
            }
        }
        for (Certificate cert : certList) {
            if (cert.getCertType().equals("博士学位证书")) {
                res.add(cert);
            }
        }
        certList.removeAll(res);
        res.addAll(certList);
        return new ReturnInfo(200, "证书包查询成功", JSON.toJSON(res));
    }

    @Override
    public ReturnInfo wxGetCertsByUId(List<Long> certUIdList) throws WXSystemBusyException {
        List<Certificate> certList = new ArrayList<>();
        for (Long UId : certUIdList) {
            Certificate cert = certificateMapper.findByCertUid(UId);
            if (cert == null) {
                throw new WXSystemBusyException();
            }
            certList.add(cert);
        }
        return new ReturnInfo(200, "证书信息查询成功", JSON.toJSON(certList));
    }

    /*
     *
     *
     *
     *
     * Utils
     *
     *
     *
     *
     */
    @Override
    public String makeToken(String openId) {
        long EXPIRATION = 3 * 24 * 60 * 60L;
        try {
            // 生成 Token
            String token = Jwts.builder()
                    // 设置 Token 签发者 可选
                    .setIssuer("时长两分半练习生")
                    // 根据用户Id设置 Token 的接受者
                    .setId(openId)
                    // 设置过期时间
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
                    // 设置 Token 生成时间 可选
                    .setIssuedAt(new Date(System.currentTimeMillis()))
                    // 通过 claim 方法设置一个 key = role，value = userRole 的值
                    // 设置加密密钥和加密算法，注意要用私钥加密且保证私钥不泄露
                    .signWith(SignatureAlgorithm.HS512, "Private Key")
                    .compact();
            return String.format("Bearer %s", token);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String parseToken(String token) {
        try {
            // 解密 Token，获取 Claims 主体
            Claims claims = Jwts.parser()
                    // 设置公钥解密，以为私钥是保密的，因此 Token 只能是自己生成的，如此来验证 Token
                    .setSigningKey("Private Key")
                    .parseClaimsJws(token).getBody();
            assert claims != null;
            // 解析 openId
            return claims.getId();
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String getOpenIdByToken() {
        String token = String.valueOf(SecurityUtils.getSubject().getPrincipal());
        return parseToken(token);
    }

    @Override
    public ReturnInfo wxUserVerify(String cardNo, String realName, String face, String back)
            throws FakeIdCardException, IdCardExpiredException, OCRInputDifferentException, UserVerifyFailed {
        JSONObject faceObj, backObj;
        Date dt = new Date();

        // OCR 识别
        // face_OCR
        faceObj = ocrIdCard(face, "face");
        // back_OCR
        backObj = ocrIdCard(back, "back");

        // 解析
        /*
         * name -   姓名
         * address - 住址
         * id -     身份证号
         * birth -  生日
         * nationality - 民族
         * sex - 性别
         * startDate - 身份证有效区间 开始时间
         * endDate - 身份证有效区间 结束时间
         * issue - 颁发机构
         */
        // face
        String name = (String) faceObj.get("name");
        String address = (String) faceObj.get("address");
        String birth = (String) faceObj.get("birth");
        String id = (String) faceObj.get("num");
        String sex = (String) faceObj.get("sex");
        String nationality = (String) faceObj.get("nationality");
        boolean is_fake = Boolean.parseBoolean(String.valueOf(faceObj.get("is_fake")));
        // back
        String startDate = (String) backObj.get("start_date");
        String endDate = (String) backObj.get("end_date");
        String issue = (String) backObj.get("issue");
        // 身份证不得为假
        if (is_fake) {
            throw new FakeIdCardException();
        }
        // 身份证需要在有效期内
        if (dt.after(String2Date(endDate))) {
            throw new IdCardExpiredException();
        }
        // OCR 结果与输入 id-name 需要匹配
        if (!(name.equals(realName) && id.equals(cardNo))) {
            throw new OCRInputDifferentException();
        }
        // 实名制核验
        if (!GlobalUtils.verify(cardNo, realName)) {
            throw new UserVerifyFailed();
        }
        // 更新用户信息
        User user = userMapper.findByOpenId(getOpenIdByToken());
        user.setUserId(cardNo);
        user.setName(realName);
        user.setSex(sex);
        userMapper.save(user);
        return new ReturnInfo(200, "实名制核验通过", null);
    }

    @Override
    public void wxExportJSON(HttpServletRequest request, HttpServletResponse response, long UId) {
        OutputStream out;
        Certificate cert = certificateMapper.findByUid(UId);
        // 创建JSON对象
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("institutionId", cert.getInstitutionId());
        jsonObject.put("certType", cert.getCertType());
        jsonObject.put("certState", cert.getCertState());
        jsonObject.put("certPic", cert.getCertPic());
        jsonObject.put("institution", cert.getInstitution());
        jsonObject.put("issueDate", cert.getIssueDate());
        jsonObject.put("certId", cert.getCertId());
        jsonObject.put("education", cert.getEducation());
        jsonObject.put("academy", cert.getAcademy());
        jsonObject.put("major", cert.getMajor());
        jsonObject.put("admissionDate", cert.getAdmissionDate());
        jsonObject.put("graduationDate", cert.getAdmissionDate());
        jsonObject.put("name", cert.getName());
        jsonObject.put("sex", cert.getSex());
        jsonObject.put("birth", cert.getBirth());
        jsonObject.put("userPic", cert.getUserPic());
        jsonObject.put("userId", cert.getUserId());
        jsonObject.put("uid", cert.getUid());
        jsonObject.put("fileHash", getFileHash(cert));

        // 写入JSON文件到前端
        String JSON_name = "数字证书";
        String fileName = JSON_name + ".json";
        try {
            fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), "iso8859-1");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.addHeader("Pargam", "no-cache");
            response.addHeader("Cache-Control", "no-cache");
            response.flushBuffer();
            byte[] buffer = new byte[1024];
            out = response.getOutputStream();
            String json = String.valueOf(jsonObject);
            // 文件加密
            String encryptJSON = encode(AESUtil.key, json);
            InputStream fis = new ByteArrayInputStream(encryptJSON.getBytes());
            BufferedInputStream bis = new BufferedInputStream(fis);
            int i = bis.read(buffer);
            while (i != -1) {
                out.write(buffer, 0, i);
                i = bis.read(buffer);
            }
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public ReturnInfo wxShare() throws InvalidAccessTokenException, WXSystemBusyException {
        // 获取 access_token
        String access_token = getAccessToken();
        // createActivityId
        String openId = getOpenIdByToken();
        String activityId = createActivityId(access_token, openId);
        return new ReturnInfo(200, "success", activityId);
    }

    @Override
    public ReturnInfo wxApplicationCodeExport(String path) {
        String filePath = "chain/wx/acode";
        // 获取 access_token
        String access_token = getAccessToken();
        // access_token 需要放在网址中
        String url = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + access_token;
        JSONObject param = new JSONObject();
        // 设置请求头信息
        HttpHeaders headers = new HttpHeaders();
        // 以json格式提交
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        // 以表单的形式提交
        // headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        // 设置请求参数
        param.put("path", path);
        HttpEntity<String> formEntity = new HttpEntity<>(param.toString(), headers);
        ResponseEntity<byte[]> entity = restTemplate.postForEntity(url, formEntity, byte[].class);
        byte[] data = entity.getBody();
        filePath = filePath + "/" + UUID.randomUUID() + ".png";
        InputStream inputStream = new ByteArrayInputStream(data);
        File file = new File(TOMCAT_PATH + filePath);

        OutputStream outputStream = null;
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            outputStream = new FileOutputStream(file);
            int len = 0;
            byte[] buf = new byte[1024];
            while ((len = inputStream.read(buf, 0, 1024)) != -1) {
                outputStream.write(buf, 0, len);
            }
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return new ReturnInfo(200, "请求成功", WEB_PATH + filePath);
    }

    @Override
    public ReturnInfo wxGetInvalidCerts() throws NoAuthenticationException, CertificateNotFoundException {
        // 获取用户身份
        String openId = getOpenIdByToken();
        String userId = userMapper.findByOpenId(openId).getUserId();
        if (userId == null) {
            throw new NoAuthenticationException();
        }
        List<Revoke> revokeList = revokeMapper.findByUserId(userId);
        List<RevokeObj> revokeObjList = new ArrayList<>();
        for (Revoke revoke : revokeList) {
            RevokeObj revokeObj = new RevokeObj();
            Certificate cert = certificateMapper.findByUid(revoke.getCert_uid());
            if (cert == null) {
                throw new CertificateNotFoundException();
            }
            revokeObj.setCertificate(cert);
            revokeObj.setRevoke(revoke);
            revokeObjList.add(revokeObj);
        }
        return new ReturnInfo(200, "撤销证书列表查询成功", JSON.toJSON(revokeObjList));
    }

    @Override
    public ReturnInfo wxUserVerifyFake(String cardNo, String realName, String face,
                                       String back) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("cardNo", cardNo);
        jsonObject.put("realName", realName);
        jsonObject.put("face_URL", face);
        jsonObject.put("back_URL", back);
        User user = userMapper.findByOpenId(getOpenIdByToken());
        user.setUserId(cardNo);
        user.setName(realName);
        user.setSex("男");
        userMapper.save(user);
        return new ReturnInfo(200, "实名制核验通过", jsonObject);
    }

    @Override
    public ReturnInfo wxUserVerifyWithLink(String cardNo, String realName, String face, String back) throws FakeIdCardException, IdCardExpiredException, OCRInputDifferentException, UserVerifyFailed {
        JSONObject faceObj, backObj;
        Date dt = new Date();
        // OCR 识别
        // face_OCR
        faceObj = ocrIdCard(face, "face");
        // back_OCR
        backObj = ocrIdCard(back, "back");

        // 解析
        /*
         * name -   姓名
         * address - 住址
         * id -     身份证号
         * birth -  生日
         * nationality - 民族
         * sex - 性别
         * startDate - 身份证有效区间 开始时间
         * endDate - 身份证有效区间 结束时间
         * issue - 颁发机构
         */
        // face
        String name = (String) faceObj.get("name");
        String address = (String) faceObj.get("address");
        String birth = (String) faceObj.get("birth");
        String id = (String) faceObj.get("num");
        String sex = (String) faceObj.get("sex");
        String nationality = (String) faceObj.get("nationality");
        boolean is_fake = Boolean.parseBoolean(String.valueOf(faceObj.get("is_fake")));
        // back
        String startDate = (String) backObj.get("start_date");
        String endDate = (String) backObj.get("end_date");
        String issue = (String) backObj.get("issue");
        // 身份证不得为假
        if (is_fake) {
            throw new FakeIdCardException();
        }
        // 身份证需要在有效期内
        if (dt.after(String2Date(endDate))) {
            throw new IdCardExpiredException();
        }
        // OCR 结果与输入 id-name 需要匹配
        if (!(name.equals(realName) && id.equals(cardNo))) {
            throw new OCRInputDifferentException();
        }
        // 实名制核验
        if (!GlobalUtils.verify(cardNo, realName)) {
            throw new UserVerifyFailed();
        }
        // 更新用户信息
        User user = userMapper.findByOpenId(getOpenIdByToken());
        user.setUserId(cardNo);
        user.setName(realName);
        user.setSex(sex);
        userMapper.save(user);
        return new ReturnInfo(200, "实名制核验通过", null);
    }

    @Override
    public String getAccessToken() {
        // 获取 access_token
        String baseUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
        String url = baseUrl + "&appid=" + AppId + "&secret=" + AppSecret;
        ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
        JSONObject jsonObject = JSON.parseObject(entity.getBody());
        return (String) jsonObject.get("access_token");
    }

    @Override
    public String createActivityId(String access_token, String openId) throws WXSystemBusyException, InvalidAccessTokenException {
        String baseUrl = "https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?";
        String url = baseUrl + "access_token=" + access_token + "&openid=" + openId;
        ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
        JSONObject jsonObject = JSON.parseObject(entity.getBody());
        String errCode = String.valueOf(jsonObject.get("errcode"));
        if (errCode.equals("-1")) {
            throw new WXSystemBusyException();
        }
        if (errCode.equals("42001")) {
            throw new InvalidAccessTokenException();
        }
        return (String) jsonObject.get("activity_id");
    }

    @Override
    public ReturnInfo wxUploadUserPic(MultipartFile file) throws FileEmptyException, WrongFileTypeException, FileUploadException {
        List<UploadPath> paths = filesUpload(new MultipartFile[]{file}, TOMCAT_PATH + ID_CARD_PATH);
        String newFileName = paths.get(0).getNewFileName();
        return new ReturnInfo(200, "文件上传成功", WEB_PATH + ID_CARD_PATH + newFileName);
    }
}
